﻿//==============================================================================
// File: MetaPropertyManagerBase.cs
// Created: 2010-05-10
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with WpfRcp. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using WpfRcp.PropertyModel.Scope;
using Res = WpfRcp.PropertyModel.Properties.Resources;

namespace WpfRcp.PropertyModel.Core
{
    /// <summary>
    /// Base class for entities implementing <see cref="IMetaPropertyManager"/> interface.
    /// </summary>
    [Serializable]
    public abstract class MetaPropertyManagerBase : IMetaPropertyManager, IDisposable
    {
        /// <summary>
        /// Represents an associated instance.
        /// </summary>
        private readonly IPropertyContainer m_PropertyContainer;

        #region Public Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaPropertyManagerBase"/> class.
        /// </summary>
        /// <param name="propertyContainer">An instance of a <see cref="IPropertyContainer"/>.</param>
        protected MetaPropertyManagerBase(IPropertyContainer propertyContainer)
        {
            if (propertyContainer == null)
                throw new ArgumentNullException("propertyContainer");
            
            m_PropertyContainer = propertyContainer;
        }

        #endregion

        #region Public Property Management

        /// <summary>
        /// Enumerates over all available properties.
        /// </summary>
        protected abstract IEnumerable<MetaProperty> EnumerateMetaProperties();

        public virtual MetaPropertyCollection GetMetaProperties()
        {
            ThrowIfDisposed();

            return new MetaPropertyCollection(EnumerateMetaProperties());
        }

        public abstract MetaPropertyCollection GetDynamicMetaProperties();

        public virtual MetaProperty GetMetaProperty(string name)
        {
            ThrowIfDisposed();

            foreach (var metaProperty in EnumerateMetaProperties())
            {
                if (metaProperty.Name == name)
                    return metaProperty;
            }

            return null;
        }

        public abstract void AddDynamicProperty(MetaProperty metaProperty);

        public abstract void RemoveDynamicProperty(MetaProperty metaProperty);

        #endregion

        #region Public Property Scope Management

        public abstract void AddPropertyScope(IPropertyScope scope);

        public abstract void RemovePropertyScope(IPropertyScope scope);

        #endregion

        #region Public GetProperty Implementation

        public abstract IEnumerable<IProperty> GetProperties();

        public abstract object GetPropertyValue(MetaProperty metaProperty);

        public abstract IProperty GetProperty(MetaProperty property);

        #endregion

        #region Public SetPropertyValue Implementation

        public virtual void SetPropertyValue(MetaPropertyKey propertyKey, object value)
        {
            ThrowIfDisposed();

            if (propertyKey == null)
                throw new ArgumentNullException("propertyKey");

            SetProperty(propertyKey.MetaProperty, value, false);
        }

        public virtual void SetPropertyValue(MetaProperty metaProperty, object value)
        {
            ThrowIfDisposed();

            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            SetProperty(metaProperty, value, true);
        }

        private void SetProperty(MetaProperty metaProperty, object value, bool checkReadOnly)
        {
            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (checkReadOnly)
            {
                if (metaProperty.IsReadOnly)
                    throw new MetaPropertyException(string.Format(Res.ExceptionPropertyReadOnly, metaProperty.Name));
            }

            Type type = metaProperty.PropertyType;

            if (value != null && !type.IsAssignableFrom(value.GetType()))
                throw new MetaPropertyException(string.Format(Res.ExceptionInvalidType, metaProperty.Name));

            if (value == null && type.IsValueType)
            {
                if (!(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)))
                    throw new MetaPropertyException(string.Format(Res.ExceptionInvalidType, metaProperty.Name));
            }

            var property = GetProperty(metaProperty);
            property.Value = value;
        }

        #endregion

        #region Public Property Factory

        protected virtual IProperty CreateProperty(MetaProperty metaProperty, PropertyMetadata metadata)
        {
            ThrowIfDisposed();

            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (metadata == null)
                throw new ArgumentNullException("metadata");

            IProperty property;
            if (metaProperty.PropertyKind == MetaPropertyKind.Transient)
            {
                property = new TransientProperty(metaProperty, PropertyContainer);
            }
            else if (metadata.ContainsOption(PropertyOptions.RevalidatesOnStateChanged))
            {
                var notifiableProperty = new NotifiableProperty(metaProperty);
                notifiableProperty.ObjectStateChanged += OnValueStateChanged;
                property = notifiableProperty;
                property.Value = GetDefaultValue(property.MetaProperty, metadata);
            }
            else
            {
                property = new Property(metaProperty);
                property.Value = GetDefaultValue(property.MetaProperty, metadata);
            }

            return property;
        }

        private void OnValueStateChanged(object sender, EventArgs e)
        {
            var property = (IProperty)sender;
            PropertyContainer.ValidateProperty(property.MetaProperty);
        }

        public virtual object GetDefaultValue(MetaProperty metaProperty, PropertyMetadata metadata)
        {
            ThrowIfDisposed();

            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (metadata == null)
                throw new ArgumentNullException("metadata");

            if (metadata.DefaultValue != null)
            {
                // Explicitly set default value
                return metadata.DefaultValue;
            }

            if (metadata.DefaultValueCallback != null)
            {
                // Explicitly set default value using callback
                return metadata.DefaultValueCallback.Invoke();
            }

            if (metaProperty.PropertyType.IsValueType)
            {
                // No default value was set and the property CLR type is a value type,
                // create default value
                return Activator.CreateInstance(metaProperty.PropertyType);
            }

            // No default value was set and the property CLR type is a reference type,
            // assign null
            return null;
        }

        #endregion

        #region Protected Properties

        /// <summary>
        /// Gets the reference to the <see cref="IPropertyContainer"/> instance which owns
        /// this manager.
        /// </summary>
        protected IPropertyContainer PropertyContainer
        {
            get { return m_PropertyContainer; }
        }

        #endregion

        #region IDisposable Implementation

        [NonSerialized]
        private bool m_Disposed;

        /// <summary>
        /// Returns true if object has been disposed.
        /// </summary>
        protected bool Disposed
        {
            get
            {
                lock (this)
                {
                    return m_Disposed;
                }
            }
        }

        /// <summary>
        /// Performs cleaning. Always call base class's <see cref="Dispose(bool)"/> method.
        /// </summary>
        /// <param name="disposing">True is this method is called via <see cref="Dispose"/> method.
        /// False indicates that the method has been called from destructor.</param>
        /// <remarks>
        /// Generally, you should dispose both managed and unmanaged resources when <paramref name="disposing"/>
        /// is true, and only unmanaged resources when <paramref name="disposing"/> is false.
        /// </remarks>
        protected virtual void Dispose(bool disposing)
        {
        }

        public void Dispose()
        {
            lock (this)
            {
                if (m_Disposed == false)
                {
                    Dispose(true);
                    m_Disposed = true;

                    GC.SuppressFinalize(this);
                }
            }
        }

        /// <summary>
        /// Destructor will run only if <see cref="Dispose"/> wasn't called.
        /// Do not provide descructors in derived types. Use <see cref="Dispose(bool)"/> instead.
        /// </summary>
        ~MetaPropertyManagerBase()
        {
            Dispose(false);
        }

        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().Name);
        }

        #endregion
    }
}